using System;
using System.Windows.Forms;
using TFlex;
using TFlex.Command;
using TFlex.Model;
using TFlex.Model.Model3D;
using TFlex.Model.Model3D.Geometry;

namespace External3D
{
	// Cube creation/editing automaton
	//
	// Автомат создания/редактирования свойств куба
	class CubeCreateCommand : PluginCommand
	{
		// External operation which is being edited
		//
		// Редактируемая внешняя операция. Если объект находится в режиме редактирования уже существующей внешней операции
		protected ExternalOperation m_editCubeOperation = null;
		public ExternalOperation EditCubeOperation
		{
			set
			{
				m_editCubeOperation = value;

				Cube cube = m_editCubeOperation.ConstObject as Cube;
				m_edgeLength = cube.EdgeLength;
				m_firstDirection = cube.FirstDirection;
				m_secondDirection = cube.SecondDirection;
				m_centre = cube.Centre;
				m_name = m_editCubeOperation.Name;
				m_node = m_editCubeOperation.GetReference((int)Cube.KeyReference.CentreID) as Node3D;
				m_firstPlane = m_editCubeOperation.GetReference((int)Cube.KeyReference.FirstDirectionID) as TFlex.Model.Model3D.Workplane;
				m_secondPlane = m_editCubeOperation.GetReference((int)Cube.KeyReference.SecondDirectionID) as TFlex.Model.Model3D.Workplane;
			}
		}

		// Length of cube edges
		//
		// Длина ребра куба
		protected double m_edgeLength;

		// Cube center
		//
		// Центр куба
		protected Point3D m_centre;
		protected Node3D m_node;

		// Direction of one of cube edges
		//
		// Направление одной стороны куба
		protected Direction m_firstDirection;

		// Direction of another cube edge
		//
		// Направление другой стороны куба
		protected Direction m_secondDirection;

		// Operation name
		//
		// Имя операции
		protected String m_name;

		// Plane whose normal is taken as the direction of one of cube edges
		//
		// Плоскость, по нормали к которой берётся направление одной стороны куба
		protected TFlex.Model.Model3D.Workplane m_firstPlane;

		// Plane whose normal is taken as the direction of another cube edge
		//
		// Плоскость, по нормали к которой берётся направление другой стороны куба
		protected TFlex.Model.Model3D.Workplane m_secondPlane;

		// Current state of this automaton
		//
		// Текущее состояние автомата
		protected State m_state;

		// Application which created this automaton
		//
		// Экземпляр класса внешнего приложения
		protected External3DPlugin m_plugin;

		// Manipulator which can be used to dynamically edit cube parameters
		//
		// Манипулятор для динамического редактирования свойств кубика
		protected CubeManipulator m_manipulator;

		// Measurement unit converter
		//
		// Конвертер единиц измерения документа в метры и из метров
		protected UnitConverter m_converter;

		// Constructs an automaton
		//
		// Конструктор
		public CubeCreateCommand(Plugin app)
			: base(app)
		{
			m_plugin = app as External3DPlugin;

			ReInit();

			// Register event handlers
			//
			// Регистрация обработчиков событий
			Initialize += CreateCommand_Initialize;
			Exit += CreateCommand_Exit;
			KeyPressed += CreateCommand_KeyPressed;
			Select += CreateCommand_Select;
		}

		public void EditProperties()
		{
			CubeDialog dialog = new CubeDialog();
			dialog.OperationName = m_name;
			dialog.EdgeLength = m_edgeLength;
			dialog.CentreCoords = m_centre;
			dialog.FirstDirection = m_firstDirection;
			dialog.SecondDirection = m_secondDirection;
			dialog.EditCentre = m_node == null;
			dialog.EditFirstDirection = m_firstPlane == null;
			dialog.EditSecondDirection = m_secondPlane == null;

			if (dialog.ShowDialog(null) == DialogResult.OK)
			{
				m_name = dialog.OperationName;
				m_edgeLength = dialog.EdgeLength;
				m_centre = dialog.CentreCoords;
				m_firstDirection = dialog.FirstDirection;
				m_secondDirection = dialog.SecondDirection;
				UpdateManipulator();
			}
		}

		// Resets object parameters to their default values
		//
		// Сбросить параметры редактируемого объекта в начальные значения
		protected void ReInit()
		{
			m_edgeLength = 10.0;
			m_centre = new Point3D(0.0, 0.0, 0.0);
			m_firstDirection = new Direction(1.0, 0.0, 0.0);
			m_secondDirection = new Direction(0.0, 1.0, 0.0);
			m_state = State.Nothing;
			m_name = "";

			if (m_node != null)
				m_node.Document.Selection.Deselect(m_node);
			if (m_firstPlane != null)
				m_firstPlane.Document.Selection.Deselect(m_firstPlane);
			if (m_secondPlane != null)
				m_secondPlane.Document.Selection.Deselect(m_secondPlane);

			m_node = null;
			m_firstPlane = null;
			m_secondPlane = null;
		}

		// Creates manipulator for dynamic editing of cube parameters
		//
		// Создать манипулятор
		protected void CreateManipulator(Document document)
		{
			m_manipulator = new CubeManipulator(document, m_plugin);
			m_converter = new UnitConverter(document);
			m_manipulator.Centre = m_converter.ToMeter(m_centre);
			m_manipulator.FirstDirection = m_firstDirection;
			m_manipulator.SecondDirection = m_secondDirection;
			m_manipulator.EdgeLength = m_converter.ToMeter(m_edgeLength);
			m_manipulator.Released += OnReleased;
			m_manipulator.Start();
		}

		// Makes the manipulator reflect the current state of this automaton
		//
		// Обновить параметры манипулятора новыми значениями
		protected void UpdateManipulator()
		{
			m_manipulator.Stop();
			m_manipulator.Centre = m_converter.ToMeter(m_centre);
			m_manipulator.FirstDirection = m_firstDirection;
			m_manipulator.SecondDirection = m_secondDirection;
			m_manipulator.EdgeLength = m_converter.ToMeter(m_edgeLength);
			m_manipulator.Start();
		}

		// Updates automenu commands
		//
		// Обновление команд автоменю
		protected void UpdateAutomenu()
		{
			bool haveSnaps = m_node != null || m_firstPlane != null || m_secondPlane != null;
			TFlex.Command.Button[] buttons = new TFlex.Command.Button[]
				{
					new DefaultButton(DefaultButton.Kind.OK),
					new DefaultButton(DefaultButton.Kind.Parameters),
					new DefaultButton(DefaultButton.Kind.Exit),
					new SeparatorButton(),
					new SingleButton((int)CubeAutomenuCommands.CentrePoint,
					                 KeyCode.keyC,
					                 m_state == State.SelectCentre ? TFlex.Command.Button.Style.Checked : TFlex.Command.Button.Style.Default),
					new SingleButton((int)CubeAutomenuCommands.FirstEdgeDirection,
					                 KeyCode.keyF,
					                 m_state == State.SelectFirstDirection ? TFlex.Command.Button.Style.Checked : TFlex.Command.Button.Style.Default),
					new SingleButton((int)CubeAutomenuCommands.SecondEdgeDirection,
					                 KeyCode.keyS,
					                 m_state == State.SelectSecondDirection ? TFlex.Command.Button.Style.Checked : TFlex.Command.Button.Style.Default),
					new SeparatorButton(),
					new SingleButton((int)CubeAutomenuCommands.CancelSnaps,
					                 KeyCode.keyK,
					                 haveSnaps ? TFlex.Command.Button.Style.Default : TFlex.Command.Button.Style.Disabled),
				};
			Automenu = new Automenu(buttons);
		}

		// Initializes this automaton
		//
		// При создании автомата создаёт манипулятор
		private void CreateCommand_Initialize(object sender, InitializeEventArgs e)
		{
			if (m_editCubeOperation != null)
				m_editCubeOperation.RollbackToParents();
			if (m_node != null)
				m_editCubeOperation.Document.Selection.Select(m_node);
			if (m_firstPlane != null)
				m_editCubeOperation.Document.Selection.Select(m_firstPlane);
			if (m_secondPlane != null)
				m_editCubeOperation.Document.Selection.Select(m_secondPlane);

			UpdateAutomenu();
			CreateManipulator(e.Document);
		}

		// Prepares this automaton for destruction
		//
		// При завершении автомата удаляется манипулятор
		private void CreateCommand_Exit(object sender, ExitEventArgs e)
		{
			if (m_editCubeOperation != null)
				m_editCubeOperation.CancelRollback();
			if (m_node != null)
				e.Document.Selection.Deselect(m_node);
			if (m_firstPlane != null)
				e.Document.Selection.Deselect(m_firstPlane);
			if (m_secondPlane != null)
				e.Document.Selection.Deselect(m_secondPlane);

			m_manipulator.Stop();
		}

		// Handles automenu commands
		//
		// Обработчик команд автоменю
		private void CreateCommand_KeyPressed(object sender, TFlex.Command.KeyEventArgs e)
		{
			switch (e.Code)
			{
			case KeyCode.LMB:
				{
					switch (m_state)
					{
					case State.SelectCentre:
						Node3D node = SelectByEvent(e, true) as Node3D;
						if (node != null)
						{
							if (m_node != null)
								e.Document.Selection.Deselect(m_node);

							m_node = node;
							m_centre = m_converter.FromMeter(m_node.Geometry.Point);
							UpdateManipulator();
							m_state = State.SelectFirstDirection;
							UpdateAutomenu();
						}
						break;

					case State.SelectFirstDirection:
						TFlex.Model.Model3D.Workplane firstPlane = SelectByEvent(e, true) as TFlex.Model.Model3D.Workplane;
						if (firstPlane != null)
						{
							if (m_firstPlane != null)
								e.Document.Selection.Deselect(m_firstPlane);

							m_firstPlane = firstPlane;
							m_firstDirection = new Direction(m_firstPlane.Geometry.Direction);
							UpdateManipulator();
							m_state = State.SelectSecondDirection;
							UpdateAutomenu();
						}
						break;

					case State.SelectSecondDirection:
						TFlex.Model.Model3D.Workplane secondPlane = SelectByEvent(e, true) as TFlex.Model.Model3D.Workplane;
						if (secondPlane != null)
						{
							if (m_secondPlane != null)
								e.Document.Selection.Deselect(m_secondPlane);

							m_secondPlane = secondPlane;
							m_secondDirection = new Direction(m_secondPlane.Geometry.Direction);
							UpdateManipulator();
							m_state = State.Nothing;
							UpdateAutomenu();
						}
						break;
					}
					break;
				}

			case KeyCode.keyENTER:
			case KeyCode.keyEND:
				{
					if (m_editCubeOperation != null)
					{
						e.Document.BeginChanges(External3DPlugin.Resources.GetString("commands.edit_cube"));

						Cube cube = m_editCubeOperation.VolatileObject as Cube;
						cube.EdgeLength = m_edgeLength;
						cube.Centre = m_centre;
						cube.FirstDirection = m_firstDirection;
						cube.SecondDirection = m_secondDirection;
						m_editCubeOperation.Name = m_name;

						if (m_node != null)
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.CentreID, m_node.Geometry.Point);
						else
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.CentreID, null);

						if (m_firstPlane != null)
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.FirstDirectionID, m_firstPlane.Geometry.Direction);
						else
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.FirstDirectionID, null);

						if (m_secondPlane != null)
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.SecondDirectionID, m_secondPlane.Geometry.Direction);
						else
							m_editCubeOperation.SetGeomReference((int)Cube.KeyReference.SecondDirectionID, null);

						e.Document.EndChanges();
						Terminate();
					}
					else
					{
						e.Document.BeginChanges(External3DPlugin.Resources.GetString("commands.create_cube"));

						Cube cube = new Cube();
						cube.EdgeLength = m_edgeLength;
						cube.Centre = m_centre;
						cube.FirstDirection = m_firstDirection;
						cube.SecondDirection = m_secondDirection;

						ExternalOperation operation = new ExternalOperation(cube, e.Document, Owner);
						if (m_name != null && m_name.Length > 0)
							operation.Name = m_name;
						if (m_node != null)
							operation.SetGeomReference((int)Cube.KeyReference.CentreID, m_node.Geometry.Point);
						if (m_firstPlane != null)
							operation.SetGeomReference((int)Cube.KeyReference.FirstDirectionID, m_firstPlane.Geometry.Direction);
						if (m_secondPlane != null)
							operation.SetGeomReference((int)Cube.KeyReference.SecondDirectionID, m_secondPlane.Geometry.Direction);

						e.Document.EndChanges();
						m_manipulator.Stop();
						ReInit();
						CreateManipulator(e.Document);
						UpdateAutomenu();
					}
					break;
				}

			case KeyCode.keyESCAPE:
			case KeyCode.RMB:
				{
					if (m_editCubeOperation != null)
						e.Document.ApplyChanges();

					Terminate();
					break;
				}

			case KeyCode.keyP:
				{
					EditProperties();
					break;
				}

			case KeyCode.keyC:
				{
					m_state = m_state == State.SelectCentre ? State.Nothing : State.SelectCentre;
					UpdateAutomenu();
					break;
				}

			case KeyCode.keyF:
				{
					m_state = m_state == State.SelectFirstDirection ? State.Nothing : State.SelectFirstDirection;
					UpdateAutomenu();
					break;
				}

			case KeyCode.keyS:
				{
					m_state = m_state == State.SelectSecondDirection ? State.Nothing : State.SelectSecondDirection;
					UpdateAutomenu();
					break;
				}

			case KeyCode.keyK:
				{
					if (m_node != null)
						e.Document.Selection.Deselect(m_node);
					if (m_firstPlane != null)
						e.Document.Selection.Deselect(m_firstPlane);
					if (m_secondPlane != null)
						e.Document.Selection.Deselect(m_secondPlane);

					m_node = null;
					m_firstPlane = null;
					m_secondPlane = null;
					UpdateAutomenu();
					break;
				}
			}
		}

		// Handles object selection events. This method is called to determine whether a particular object can be
		// selected or not
		//
		// Обработчик события предварительной фильтрации выбираемых объектов. Используется для настройки фильтров для
		// разных состояний автомата
		private void CreateCommand_Select(object sender, SelectEventArgs e)
		{
			switch (e.Mode)
			{
			case TFlex.Command.SelectEventArgs.SelectMode.SelectObject:
				{
					switch (e.GroupType)
					{
					case ObjectType.Node3D:
						if (m_state == State.SelectCentre)
							e.Object = e.Object as Node3D;
						else
							e.Object = null;
						break;

					case ObjectType.WorkSurface:
						if (m_state == State.SelectFirstDirection || m_state == State.SelectSecondDirection)
							e.Object = e.Object as TFlex.Model.Model3D.Workplane;
						else
							e.Object = null;
						break;

					default:
						e.Object = null;
						break;
					}
					break;
				}

			case TFlex.Command.SelectEventArgs.SelectMode.SelectType:
				{
					switch (e.GroupType)
					{
					case ObjectType.Node3D:
						if (m_state == State.SelectCentre)
							e.Result = true;
						else
							e.Result = false;
						break;

					case ObjectType.WorkSurface:
						if (m_state == State.SelectFirstDirection || m_state == State.SelectSecondDirection)
							e.Result = true;
						else
							e.Result = false;
						break;

					default:
						e.Result = false;
						break;
					}
					break;
				}
			}
		}

		// This method is called when the user changes cube parameter(s) by using the manipulator
		//
		// Уведомление об изменении свойств кубика через манипулятор
		private void OnReleased(BaseManipulator.IDManipulator manipulator)
		{
			switch (manipulator)
			{
			// Cube orientation has been changed
			//
			// Если изменилась ориентация в пространстве
			case BaseManipulator.IDManipulator.Orientation:
				m_firstDirection = m_manipulator.FirstDirection;
				m_secondDirection = m_manipulator.SecondDirection;

				if (m_firstPlane != null)
				{
					m_firstPlane.Document.Selection.Deselect(m_firstPlane);
					m_firstPlane = null;
					UpdateAutomenu();
				}

				if (m_secondPlane != null)
				{
					m_secondPlane.Document.Selection.Deselect(m_secondPlane);
					m_secondPlane = null;
					UpdateAutomenu();
				}

				break;

			// Cube position has been changed
			//
			// Если изменились координаты точки
			case BaseManipulator.IDManipulator.Point:
				m_centre = m_converter.FromMeter(m_manipulator.Centre);

				if (m_node != null)
				{
					m_node.Document.Selection.Deselect(m_node);
					m_node = null;
					UpdateAutomenu();
				}
				break;

			// Cube has been resized
			//
			// Если изменились размеры
			case BaseManipulator.IDManipulator.Linear:
				m_edgeLength = m_converter.FromMeter(m_manipulator.EdgeLength);
				break;
			}
		}

		// States in which this automaton can be
		//
		// Состояния, в которых может находится автомат
		protected enum State
		{
			// Nothing happens
			//
			// Ничего не происходит
			Nothing,

			// Cube center is being selected
			//
			// Режим выбора центра на основе геометрических данных других объектов модели
			SelectCentre,

			// Direction of one of cube edges is being selected
			//
			// Режим выбора одного направления грани куба на основе геометрических данных других объектов модели
			SelectFirstDirection,

			// Direction of another cube edge is being selected
			//
			// Режим выбора другого направления грани куба на основе геометрических данных других объектов модели
			SelectSecondDirection,
		};
	}
}
